/******************************************************************************************************
Copyright (C) 2017 Mestrelab Research S.L. All rights reserved.

This file is part of the MNova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*****************************************************************************************************/

/*globals date2ISOString, mainWindow, qsTr, settings, Application, Dir, ExportMnovaConfiguration_Engine, File, FileDialog, IniFile, MessageBox, MnUi, ProgressDialog, Settings, TextStream*/

/*jslint plusplus: true, continue:true, indent: 4*/

function ExportMnovaConfiguration() {
	"use strict";

	var showDialog, data, outputFilename, parameters, exportMnovaConfigurationEngine,
		outputFilenameKey, settingsTreeStateKey, exportTreeObject, stringObjectState,
		seettingsTreeStateObject;

	//Instance Attributes
	this.dialog = {};
	this.flagCellChanged = false;
	this.checkBoxFieldNumber = 0;
	this.pathFieldNumber = 1;
	this.mnovaRootNodeName = "Mnova Root";
	this.totalRowsChecked = 0;

	//Registry entries
	outputFilenameKey = "Export Mnova Configuration/Output Filename";
	settingsTreeStateKey = "Export Mnova Configuration/Settings Tree State";

	//Load GUI
	this.dialog = Application.loadUiFile('ricares:exportMnovaConfiguration.ui');
	showDialog = true;

	//Call to the engine in order to get the tree object representation of the Mnova seetings
	parameters = {};
	exportMnovaConfigurationEngine = new ExportMnovaConfiguration_Engine(parameters);

	//Get the structure tree data
	exportTreeObject = exportMnovaConfigurationEngine.process();

	//Sort the array of objects by the node path property.
	exportMnovaConfigurationEngine.folderListArray = exportMnovaConfigurationEngine.folderListArray.sort(
		function (a, b) {
			return Dir.alphanumericPathsCompare(a.path, b.path);
		}
	);

	//-------------------------------INSTANCE METHODS-------------------------
	this.assignValuesToTableFromTreeObject = function (aFolderListArray, aTableWidgetItems, aTreeMapObject, aCounterObject) {

		var i, key, currentRow, checkboxIndex, treeNodeIndex, treeNodeValue;

		if (aTreeMapObject !== undefined) {

			if (aCounterObject === undefined) {
				aCounterObject = {};
			}

			if (aCounterObject.currentDepth === undefined || isNaN(aCounterObject.currentDepth)) {
				aCounterObject.currentDepth = 1;
			}

			if (aCounterObject.currentRow === undefined || isNaN(aCounterObject.currentRow)) {
				aCounterObject.currentRow = 1;
			}

			for (key in aTreeMapObject) {

				if (aTreeMapObject.hasOwnProperty(key)) {

					//It drops the "metadata" propertoies
					if (key !== "marked" && key !== "numberOfSettingsFolder" && key !== "maximumDepth" && key !== "maximumDepthFolder") {

						//Get indexes and tree node name
						currentRow = aCounterObject.currentRow - 1;
						checkboxIndex = this.checkBoxFieldNumber;
						if (key === "") {
							treeNodeValue = this.mnovaRootNodeName;
						} else {
							treeNodeValue = key;
						}

						//Add checkbox
						aTableWidgetItems[currentRow][checkboxIndex].text = "";
						aTableWidgetItems[currentRow][checkboxIndex].flags = 49;
						aTableWidgetItems[currentRow][checkboxIndex].checkState = 0;

						//Add tree node
						aTableWidgetItems[currentRow][aCounterObject.currentDepth].text = treeNodeValue;
						aTableWidgetItems[currentRow][aCounterObject.currentDepth].flags = 33;
						aTableWidgetItems[currentRow][aCounterObject.currentDepth].textAlignment = 132;
					}

					if (aTreeMapObject.hasOwnProperty(key)) {
						if (typeof aTreeMapObject[key] === 'object') {
							aCounterObject.currentRow++;
							aCounterObject.currentDepth++;
							this.assignValuesToTableFromTreeObject(aFolderListArray, aTableWidgetItems, aTreeMapObject[key], aCounterObject);
							aCounterObject.currentDepth--;
						}
					}
				}
			}

		//Option currently used due to we do not use a tree widget object
		} else {

			//Array data wich contains the path and checked Property
			for (i = 0; i < aFolderListArray.length; i++) {

				currentRow = i;
				checkboxIndex = this.checkBoxFieldNumber;
				treeNodeIndex = this.pathFieldNumber;
				treeNodeValue = aFolderListArray[i].path;
				if (treeNodeValue === "/") {
					treeNodeValue = this.mnovaRootNodeName;
				}

				//Add checkbox
				if (aTableWidgetItems[currentRow].length > 0) {
					aTableWidgetItems[currentRow][checkboxIndex].text = "";
					aTableWidgetItems[currentRow][checkboxIndex].flags = 49;
					aTableWidgetItems[currentRow][checkboxIndex].checkState = 0;

					//Add tree node
					aTableWidgetItems[currentRow][treeNodeIndex].text = treeNodeValue;
					aTableWidgetItems[currentRow][treeNodeIndex].flags = 33;
				}
			}
		}
	};

	this.secureSignalManagement = function (aSignal, aSlot, aAction, aFlag, aContext) {

		try {
			if (aAction === "connect" && !aFlag) {
				aSignal.connect(aContext, aSlot);
				aFlag = true;
			}
		} catch (e) {
			try {
				if (aAction === "connect" && !aFlag) {
					aSignal.connect(aContext, aSlot);
					aFlag = true;
				}
			} catch (er) {
				aFlag = false;
			}
		}

		try {
			if (aAction === "disconnect" && aFlag) {
				aSignal.disconnect(aContext, aSlot);
				aFlag = false;
			}
		} catch (err) {
			try {
				if (aAction === "disconnect" && aFlag) {
					aSignal.disconnect(aContext, aSlot);
					aFlag = false;
				}
			} catch (erro) {
				aFlag = true;
			}
		}

		return aFlag;
	};

	this.checkAllRows = function () {

		var i, items;

		this.flagCellChanged = this.secureSignalManagement(this.dialog.widgets.tableWidget.cellChanged, this.cellChanged, "disconnect", this.flagCellChanged, this);
		items = this.dialog.widgets.tableWidget.items;
		for (i = 0; i < items.length; i++) {
			if (items[i][this.checkBoxFieldNumber].checkState !== 2) {
				items[i][this.checkBoxFieldNumber].checkState = 2;
				this.totalRowsChecked++;
			}
		}

		this.dialog.widgets.tableWidget.items = items;
		this.dialog.widgets.toolButtonCheckAll.enabled = false;
		this.dialog.widgets.toolButtonUncheckAll.enabled = true;
		this.flagCellChanged = this.secureSignalManagement(this.dialog.widgets.tableWidget.cellChanged, this.cellChanged, "connect", this.flagCellChanged, this);
	};

	this.uncheckAllRows = function () {

		var i, items;

		this.flagCellChanged = this.secureSignalManagement(this.dialog.widgets.tableWidget.cellChanged, this.cellChanged, "disconnect", this.flagCellChanged, this);
		items = this.dialog.widgets.tableWidget.items;
		for (i = 0; i < items.length; i++) {
			if (items[i][this.checkBoxFieldNumber].checkState !== 0) {
				items[i][this.checkBoxFieldNumber].checkState = 0;
				this.totalRowsChecked--;
			}
		}

		this.dialog.widgets.tableWidget.items = items;
		this.dialog.widgets.toolButtonCheckAll.enabled = true;
		this.dialog.widgets.toolButtonUncheckAll.enabled = false;
		this.flagCellChanged = this.secureSignalManagement(this.dialog.widgets.tableWidget.cellChanged, this.cellChanged, "connect", this.flagCellChanged, this);
	};

	this.fillTableWidget = function (aTableWidget, aFolderListArray, aTreeMapObject) {

		var i, totalRows, totalColumns, auxTotalColumns, items;

		if (aTreeMapObject !== undefined) {

			totalRows = 0;
			totalColumns = 0;
			if (aTreeMapObject !== undefined) {
				if (aTreeMapObject.numberOfSettingsFolder !== undefined) {
					if (!isNaN(aTreeMapObject.numberOfSettingsFolder)) {
						//Each tree node will be represented into a row
						totalRows = aTreeMapObject.numberOfSettingsFolder;
					}
				}
				if (aTreeMapObject.maximumDepth !== undefined) {
					if (!isNaN(aTreeMapObject.maximumDepth)) {
						//We need a extra column to implement the checkbox for selection
						totalColumns = aTreeMapObject.maximumDepth + 1;
					}
				}
			}

			//Insert empty rows
			for (i = 0; i < totalRows; i++) {
				aTableWidget.insertRow(0);
			}

			//Delete previous columns and create the new ones
			auxTotalColumns = aTableWidget.columnCount;
			for (i = auxTotalColumns - 1; i >= 0; i--) {
				aTableWidget.removeColumn(i);
			}
			for (i = 0; i < totalColumns; i++) {
				aTableWidget.insertColumn(0);
			}

			//Assign field values using the object properties. Each object property is a tree node
			items = aTableWidget.items;
			this.assignValuesToTableFromTreeObject(undefined, items, aTreeMapObject, undefined);
			aTableWidget.items = items;

		} else {

			//We need a extra column to implement the checkbox for selection
			totalRows = aFolderListArray.length;
			totalColumns = aTableWidget.columnCount;

			//Insert empty rows
			for (i = 0; i < totalRows; i++) {
				aTableWidget.insertRow(0);
			}

			if (totalColumns < 2) {
				aTableWidget.horzHeaderItems = ["Selection", "FolderPath"];
			}

			//Assign field values using the object properties. Each object property is a tree node
			items = aTableWidget.items;
			this.assignValuesToTableFromTreeObject(aFolderListArray, items, aTreeMapObject, undefined);
			aTableWidget.items = items;
		}

		//Resize columns above all by the checkboxes
		aTableWidget.resizeColumnsToContents();
	};

	this.updateCheckedRows = function (aRow) {

		var i, items, counter, fieldNumber;

		this.dialog.widgets.toolButtonCheckAll.enabled = true;
		this.dialog.widgets.toolButtonUncheckAll.enabled = true;
		fieldNumber = this.checkBoxFieldNumber;

		counter = this.totalRowsChecked;
		items = this.dialog.widgets.tableWidget.items;
		if (aRow !== undefined) {
			if (items[aRow][fieldNumber].checkState === 2) {
				counter++;
			} else if ((items[aRow][fieldNumber].checkState === 0)) {
				counter--;
			}
		} else {
			counter = 0;
			for (i = 0; i < items.length; i++) {
				if (items[i][fieldNumber].checkState === 2) {
					counter++;
				}
			}
		}

		if (counter === items.length) {
			this.dialog.widgets.toolButtonCheckAll.enabled = false;
		}
		if (counter === 0) {
			this.dialog.widgets.toolButtonUncheckAll.enabled = false;
		}

		this.totalRowsChecked = counter;

		return counter;
	};

	this.inheritanceManagement = function (row, col) {

		var i, inheritanceToken, inheritanceTokenArray, items,
			auxInheritanceToken, nextNodePath, deepNodeToken;

		if (col === this.checkBoxFieldNumber) {

			//Disconnect signal to avoid entering into an endless loop
			this.flagCellChanged = this.secureSignalManagement(this.dialog.widgets.tableWidget.cellChanged, this.cellChanged, "disconnect", this.flagCellChanged, this);

			//Inheritance management
			items = this.dialog.widgets.tableWidget.items;
			if (items[row][this.checkBoxFieldNumber].checkState !== 0) {
				inheritanceToken = items[row][this.pathFieldNumber].text;
				if (inheritanceToken === this.mnovaRootNodeName) {
					i = row + 1;
					while (i < items.length) {
						if (items[i][this.checkBoxFieldNumber].checkState !== 2) {
							items[i][this.checkBoxFieldNumber].checkState = 2;
						}
						i++;
					}
					this.dialog.widgets.tableWidget.items = items;
				} else {
					inheritanceTokenArray = inheritanceToken.split("/");
					if (inheritanceTokenArray.length > 0) {
						deepNodeToken = inheritanceTokenArray.length - 1;
						auxInheritanceToken = inheritanceTokenArray[deepNodeToken];
						i = row + 1;
						while (i < items.length) {
							nextNodePath = items[i][this.pathFieldNumber].text;
							inheritanceTokenArray = nextNodePath.split("/");
							if (inheritanceTokenArray.length > 0) {
								if (inheritanceTokenArray[deepNodeToken] === auxInheritanceToken) {
									if (items[i][this.checkBoxFieldNumber].checkState !== 2) {
										items[i][this.checkBoxFieldNumber].checkState = 2;
									}
								} else {
									break;
								}
							} else {
								break;
							}
							i++;
						}
						this.dialog.widgets.tableWidget.items = items;
					}
				}
			}

			//Connect signal again to detect new events
			this.flagCellChanged = this.secureSignalManagement(this.dialog.widgets.tableWidget.cellChanged, this.cellChanged, "connect", this.flagCellChanged, this);
		}
	};

	this.cellChanged = function (row, col) {

		if (col === this.checkBoxFieldNumber) {
			this.inheritanceManagement(row, col);
			this.totalRowsChecked = this.updateCheckedRows();
		}
	};

	this.existsValueIntoArray = function (aArray, aValue, aPropertyValue) {

		var i, result, element;

		result = false;
		if (aArray !== undefined && aArray.length > 0) {
			for (i = 0; i < aArray.length; i++) {
				if (aPropertyValue !== undefined) {
					element = aArray[i];
					if (element[aPropertyValue] === aValue) {
						result = element;
						break;
					}
				} else {
					element = aArray[i];
					if (element === aValue) {
						result = true;
						break;
					}
				}
			}
		}

		return result;
	};

	this.generateIniSettingsFile = function (aArray, aIniFilePath) {

		var i, j, currentSettingsNode, rootSettings, iniFile, childKeys, childKey, keyValue, flagContentCreated, fullKeyPath;

		//Remove the previous ini File
		if (File.exists(aIniFilePath)) {
			File.remove(aIniFilePath);
		}

		//Create the new ini file
		iniFile = new IniFile(aIniFilePath);

		//Get the content keys of each selected foder
		flagContentCreated = false;
		for (i = 0; i < aArray.length; i++) {
			currentSettingsNode = aArray[i];
			if (currentSettingsNode.checked !== undefined && currentSettingsNode.checked) {
				rootSettings = new Settings(currentSettingsNode.path);
				childKeys = rootSettings.childKeys();
				for (j = 0; j < childKeys.length; j++) {
					childKey = childKeys[j];
					keyValue = rootSettings.value(childKey);
					fullKeyPath = currentSettingsNode.path + "/" + childKey;
					iniFile.setValue(fullKeyPath, keyValue);
				}
			}
		}
	};

	this.synchronizeTableWidthStructureData = function (aTableWidget, aStructureData, aUpdateTable) {

		var i, items, currentNodePath, arrayElement;

		items = aTableWidget.items;

		//Synchronize table width structure data
		if (aUpdateTable !== undefined && aUpdateTable) {
			this.flagCellChanged = this.secureSignalManagement(this.dialog.widgets.tableWidget.cellChanged, this.cellChanged, "disconnect", this.flagCellChanged, this);

			//We need to compare if all the previous nodes still exists in the current registry.

			for (i = 0; i < items.length; i++) {
				if (items[i].length > 0) {
					currentNodePath = items[i][this.pathFieldNumber].text;
					if (currentNodePath === this.mnovaRootNodeName) {
						currentNodePath = "/";
					}

					//So, we need to check if the object store in the settings has the same node as the object returned by the process method of the engine
					arrayElement = this.existsValueIntoArray(aStructureData, currentNodePath, "path");
					if (arrayElement !== undefined && typeof (arrayElement) === "object") {
						if (arrayElement.checked) {
							items[i][this.checkBoxFieldNumber].checkState = 2;
						} else if (!arrayElement.checked) {
							items[i][this.checkBoxFieldNumber].checkState = 0;
						}
					}
				}
			}
			aTableWidget.items = items;
			this.flagCellChanged = this.secureSignalManagement(this.dialog.widgets.tableWidget.cellChanged, this.cellChanged, "connect", this.flagCellChanged, this);

		//Synchronize stucture data with table
		} else {
			for (i = 0; i < items.length; i++) {
				if (items[i][this.checkBoxFieldNumber].checkState === 0) {
					aStructureData[i].checked = false;
				} else if (items[i][this.checkBoxFieldNumber].checkState === 2) {
					aStructureData[i].checked = true;
				}
			}
		}
	};

	//-----------------------------------------MAIN PROGRAM---------------------------------

	//Load settings to the GUI
	data = settings.value(outputFilenameKey, "");
	if (data !== "") {
		this.dialog.widgets.leOutputFilename.text = data;
	}

	data = settings.value(settingsTreeStateKey, "");
	if (data !== "") {
		seettingsTreeStateObject = JSON.parse(data);
	}

	//Fill the table using the array because we will use a single column table instead of a tree representation
	//Hence, exportTreeObject will not be used until we use a tree widget
	this.fillTableWidget(this.dialog.widgets.tableWidget, exportMnovaConfigurationEngine.folderListArray);
	this.synchronizeTableWidthStructureData(this.dialog.widgets.tableWidget, seettingsTreeStateObject, true);

	//Signals Management
	this.dialog.widgets.tbOutputFilename.clicked.connect(this, ExportMnovaConfiguration.openOutputFilename);
	this.dialog.widgets.toolButtonCheckAll.clicked.connect(this, this.checkAllRows);
	this.dialog.widgets.toolButtonUncheckAll.clicked.connect(this, this.uncheckAllRows);
	this.flagCellChanged = this.secureSignalManagement(this.dialog.widgets.tableWidget.cellChanged, this.cellChanged, "connect", this.flagCellChanged, this);

	//Update check and uncheck buttons states
	this.dialog.widgets.toolButtonCheckAll.enabled = true;
	this.dialog.widgets.toolButtonUncheckAll.enabled = false;
	this.updateCheckedRows();

	//Launch GUI and get the user response
	while (showDialog) {

		if (!this.dialog.exec()) {
			return;
		}

		//Input directory check control
		outputFilename = new File(this.dialog.widgets.leOutputFilename.text);
		if (this.dialog.widgets.leOutputFilename.text === undefined || this.dialog.widgets.leOutputFilename.text.trim() === "") {
			this.dialog.widgets.leOutputFilename.setFocus();
			MessageBox.critical(qsTr("The output filename is empty"));
			continue;
		}

		//Empty selection performed
		if (this.totalRowsChecked === 0) {
			this.dialog.widgets.tableWidget.setFocus();
			MessageBox.critical(qsTr("At least a row checked is needed"));
			continue;
		}

		showDialog = false;
	}

	//Synchronize the table width the structure data stored in memory
	this.synchronizeTableWidthStructureData(this.dialog.widgets.tableWidget, exportMnovaConfigurationEngine.folderListArray, false);
	stringObjectState = JSON.stringify(exportMnovaConfigurationEngine.folderListArray);

	//Generate the ini settings file
	this.generateIniSettingsFile(exportMnovaConfigurationEngine.folderListArray, this.dialog.widgets.leOutputFilename.text);

	//Save values to the registry
	settings.setValue(outputFilenameKey, this.dialog.widgets.leOutputFilename.text);
	settings.setValue(settingsTreeStateKey, stringObjectState);

	//Warning to the user that the process has finished
	MessageBox.information(qsTr("The INI file has been created successfully"));
}

//-------------------------------STATIC CLASS METHODS-------------------------
ExportMnovaConfiguration.openOutputFilename = function () {
	'use strict';

	var file, dir, filename;

	if (this.dialog.widgets.leOutputFilename.text !== "") {
		file = new File(this.dialog.widgets.leOutputFilename.text);
		dir = file.absDirPath;
		if (dir.length === 0) {
			dir = Dir.home();
		}
	} else {
		dir = Dir.home();
	}

	filename = FileDialog.getSaveFileName("*.ini", "Select Filename", dir);
	if (filename.length) {
		this.dialog.widgets.leOutputFilename.text = filename;
	}
};

function ExportMnovaConfiguration_Engine(aParameters) {
	"use strict";

	//Set instance attributes from parameters
	this.rootFolderOfMnova = "/";
	this.folderListArray = [];
	if (aParameters !== undefined) {
		if (aParameters.treeObject !== undefined) {
			this.treeObjectState = aParameters.treeObject;
		}
	}

	//---------------------------INSTANCE METHODS-------------------------------
	ExportMnovaConfiguration_Engine.prototype.assignObjectProperty = function (aObject, aKeyPath, aValue) {

		var i, lastKeyIndex, key;

		lastKeyIndex = aKeyPath.length - 1;
		for (i = 0; i < lastKeyIndex; ++i) {
			key = aKeyPath[i];
			if (!aObject.hasOwnProperty(key)) {
				if (key === "") {
					aObject[key] = {};
					continue;
				}
			}
			aObject = aObject[key];
		}
		aObject[aKeyPath[lastKeyIndex]] = aValue;
	};

	ExportMnovaConfiguration_Engine.prototype.getFolderKeys = function (aCurrent, aParent, aObject, aFolderListArray) {

		var i, setts, childs, currentFolderKey, object, currentDepth;

		//Get the right settings paths
		if (aParent === undefined || (aParent !== undefined && aParent === "/")) {
			currentFolderKey = aCurrent;
		} else {
			currentFolderKey = aParent + "/" + aCurrent;
		}

		//Manage object result
		if (aObject === undefined) {
			object = {};
			object.numberOfSettingsFolder = 0;
			object.maximumDepth = 0;
		} else {
			object = aObject;
			if (object.numberOfSettingsFolder === undefined) {
				object.numberOfSettingsFolder = 0;
				object.maximumDepth = 0;
			}
		}

		//Get an array of folders
		if (aFolderListArray === undefined) {
			aFolderListArray = [];
		}

		//Assign a new tree node as a property of the result object.
		//We also use an array if is not undefined
		aFolderListArray.push({});
		aFolderListArray[aFolderListArray.length - 1].checked = false;
		if (currentFolderKey !== "/") {
			this.assignObjectProperty(aObject, ("/" + currentFolderKey).split("/"), {marked: false});
			object.numberOfSettingsFolder++;
			currentDepth = currentFolderKey.split("/").length + 1;
			aFolderListArray[aFolderListArray.length - 1].path = "/" + currentFolderKey;
		} else {
			this.assignObjectProperty(aObject, currentFolderKey.split("/"), {marked: false});
			object.numberOfSettingsFolder++;
			object.maximumDepth = 1;
			currentDepth = 1;
			aFolderListArray[aFolderListArray.length - 1].path = currentFolderKey;
		}

		if (object.maximumDepth <= currentDepth) {
			object.maximumDepth = currentDepth;
			object.maximumDepthFolder = currentFolderKey;
		}

		//Get all items folders recursively
		i = 0;
		setts = new Settings(currentFolderKey);
		childs = setts.childGroups();
		while (i < childs.length) {
			this.getFolderKeys(childs[i], currentFolderKey, aObject, aFolderListArray);
			i++;
		}
	};

	ExportMnovaConfiguration_Engine.prototype.process = function (aStartingSettingFolder) {

		var object, startingSettingFolder;

		//Object to map the settings tree
		object = {};
		object.numberOfSettingsFolder = 0;
		object.maximumDepth = 0;

		//Get the starting tree node
		if (aStartingSettingFolder !== undefined) {
			startingSettingFolder = aStartingSettingFolder;
		} else {
			startingSettingFolder = this.rootFolderOfMnova;
		}

		//Map all folders of registry into an object
		this.getFolderKeys("/", startingSettingFolder, object, this.folderListArray);

		return object;
	};
}
function ExportMnova() {
	'use strict';

	var viewer;

	//Instance the script by using the Application global variable
	try {
		viewer = new ExportMnovaConfiguration();
	} catch (Err) {
		MessageBox.critical(Err);
	}

	return viewer;
}

if (this.MnUi && this.MnUi.scripts_common) {
	MnUi.scripts_common.scripts_common_ExportMnovaConfiguration = ExportMnova;
}
